package cn.edu.hnu.matching.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.edu.hnu.matching.common.PageResult;
import cn.edu.hnu.matching.common.Result;
import cn.edu.hnu.matching.common.ResultCode;
import cn.edu.hnu.matching.constants.UserConstants;
import cn.edu.hnu.matching.constants.WeChatConstants;
import cn.edu.hnu.matching.exception.ThrowUtils;
import cn.edu.hnu.matching.manager.RedissonManager;
import cn.edu.hnu.matching.model.dto.UserModifyAvatarDto;
import cn.edu.hnu.matching.model.dto.UserRecommendSortDto;
import cn.edu.hnu.matching.model.dto.UserSearchByKeywordDto;
import cn.edu.hnu.matching.model.entity.User;
import cn.edu.hnu.matching.mapper.UserMapper;
import cn.edu.hnu.matching.model.vo.UserDetailVo;
import cn.edu.hnu.matching.model.vo.UserSearchVo;
import cn.edu.hnu.matching.model.vo.UserWxLoginVo;
import cn.edu.hnu.matching.properties.WeChatAppProperties;
import cn.edu.hnu.matching.service.IUserService;
import cn.edu.hnu.matching.utils.RecommendUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RateIntervalUnit;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author n70huihui
 * @since 2025-04-10
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final WeChatAppProperties weChatAppProperties;

    private final RedissonManager redissonManager;

    @Override
    public Result<UserWxLoginVo> wxlogin(String code) {
        String openid = getOpenId(code);

        // 查看数据库是否存在该用户
        User user = lambdaQuery()
                .eq(User::getOpenId, openid)
                .eq(User::getIsDelete, UserConstants.UN_DELETED)
                .one();

        // 用户不存在，创建用户，然后返回
        if (ObjectUtil.isEmpty(user)) {
            user = User.builder()
                    .username(UserConstants.DEFAULT_USERNAME + RandomUtil.randomNumbers(6))
                    .gender(UserConstants.DEFAULT_GENDER)
                    .avatar(UserConstants.DEFAULT_AVATAR)
                    .description(UserConstants.DEFAULT_DESCRIPTION)
                    .role(UserConstants.USER)
                    .openId(openid)
                    .createTime(LocalDateTime.now())
                    .updateTime(LocalDateTime.now())
                    .isDelete(UserConstants.UN_DELETED)
                    .build();
            save(user);
        }

        // 调用 sa-token 登录接口，实现登录，集成 Redis
        StpUtil.login(user.getOpenId());
        UserWxLoginVo userWxLoginVo = BeanUtil.copyProperties(user, UserWxLoginVo.class, "tags");

        // 获取token
        String tokenValue = StpUtil.getTokenInfo().getTokenValue();
        userWxLoginVo.setToken(tokenValue);

        // 解析tags数组
        List<String> tags = JSONUtil.parseArray(user.getTags()).toList(String.class);
        userWxLoginVo.setTags(tags);
        return Result.success(userWxLoginVo);
    }

    /**
     * 通过微信登录凭证码换取用户 openid
     * @param code 微信登录凭证码
     * @return 用户 openid
     */
    private String getOpenId(String code) {
        String json = HttpUtil.createGet(WeChatConstants.WX_LOGIN_URL)
                .form("appid", weChatAppProperties.getAppId())
                .form("secret", weChatAppProperties.getSecretKey())
                .form("js_code", code)
                .form("grant_type", "authorization_code")
                .execute()
                .body();
        // TODO JSON解析异常判断
        JSONObject response = JSONUtil.parseObj(json);
        return response.getStr("openid");
    }

    @Override
    public Result<Void> logout(String openId) {
        StpUtil.logout(openId);
        return Result.success(null);
    }

    /**
     * 辅助方法，用来修改用户对象的某一个字段
     * @param fieldGetter 字段引用接口，示例：<code>User::getGender</code>
     * @param value 字段值，示例：<code>0</code>
     * @param <T> 泛型
     */
    private <T> void updateUserField(SFunction<User, ?> fieldGetter, T value) {
        String openId = StpUtil.getLoginIdAsString();
        boolean result = lambdaUpdate()
                .set(fieldGetter, value)
                .set(User::getUpdateTime, LocalDateTime.now())
                .eq(User::getOpenId, openId)
                .eq(User::getIsDelete, UserConstants.UN_DELETED)
                .update();
        ThrowUtils.throwIf(!result, ResultCode.SYSTEM_ERROR);
    }


    @Override
    public Result<Void> modifyAvatar(UserModifyAvatarDto userModifyAvatarDto) {
        updateUserField(User::getAvatar, userModifyAvatarDto.getAvatarUrl());
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyUserName(String userName) {
        updateUserField(User::getUsername, userName);
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyGender(Short gender) {
        updateUserField(User::getGender, gender);
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyDescription(String description) {
        updateUserField(User::getDescription, description);
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyWeChatId(String weChatId) {
        updateUserField(User::getWechatId, weChatId);
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyQQId(String qqId) {
        updateUserField(User::getQqId, qqId);
        return Result.success(null);
    }

    @Override
    public Result<Void> modifyTags(List<String> tags) {
        // 转JSON串
        String tagsJson = JSONUtil.toJsonStr(tags);
        updateUserField(User::getTags, tagsJson);
        return Result.success(null);
    }

    /**
     * 随机从关系型数据库中抽出指定数量的数据
     * @param count 指定的数量
     * @return 用户数据列表
     */
    private List<UserRecommendSortDto> getRandomUsersByMySQL(int count) {
        String openId = StpUtil.getLoginIdAsString();
        List<User> userList = lambdaQuery()
                .select(User::getId, User::getTags)
                .ne(User::getOpenId, openId)
                .eq(User::getIsDelete, UserConstants.UN_DELETED)
                .last("ORDER BY RAND() LIMIT " + count)
                .list();
        return userList.stream().map(user -> {
            // 拷贝属性
            UserRecommendSortDto dto = new UserRecommendSortDto();
            BeanUtil.copyProperties(user, dto, "tags");

            // 解析tags
            dto.setTags(JSONUtil.parseArray(user.getTags()).toList(String.class));

            return dto;
        }).toList();
    }

    /**
     * 随机从缓存中抽出指定数量的数据
     * @param count 指定的数量
     * @return 用户数据列表
     */
    private List<UserRecommendSortDto> getRandomUsersByRedis(int count) {
        String openId = StpUtil.getLoginIdAsString();
        List<User> userRecommendPool = redissonManager.getUserRecommendPool();

        // 洗牌
        Collections.shuffle(userRecommendPool);

        // 取前 30 个
        List<User> users = userRecommendPool.subList(0, count);

        // 排除掉自己，并作对象数据转换
        return users.stream()
            .filter(user -> !user.getOpenId().equals(openId))
            .map(user -> {

                // 拷贝属性
                UserRecommendSortDto dto = new UserRecommendSortDto();
                BeanUtil.copyProperties(user, dto, "tags");

                // 解析tags
                dto.setTags(JSONUtil.parseArray(user.getTags()).toList(String.class));

                return dto;
            }).toList();
    }

    @Override
    public Result<List<UserSearchVo>> getHomepageRecommendation(List<String> tags) {
        // 数据推荐
        UserRecommendSortDto currentUserDto = new UserRecommendSortDto();
        currentUserDto.setTags(tags);

        // 先从缓存里随机抽30个
        List<UserRecommendSortDto> randomUsers = getRandomUsersByRedis(30);

        // 根据大标签粗排
        List<UserRecommendSortDto> roughSortByTagUsers = RecommendUtil.roughSortByTag(randomUsers, currentUserDto);

        // 根据子标签粗排
        List<UserRecommendSortDto> roughSortBySubTagUsers = RecommendUtil.roughSortBySubTag(roughSortByTagUsers, currentUserDto);

        // 最后根据标签精排
        List<UserRecommendSortDto> results = RecommendUtil.fineSort(roughSortBySubTagUsers, currentUserDto);

        // System.out.println(results);

        // 拿 id 列表
        List<Long> idList = results.stream().map(UserRecommendSortDto::getId).toList();

        // 从数据库里找对应的数据出来
        List<User> users = lambdaQuery().getBaseMapper().selectBatchIds(idList);

        List<UserSearchVo> searchVos = getSearchVosByUsers(users);

        return Result.success(searchVos);
    }

    /**
     * 把用户信息转换为 SearchVo 对象列表
     * @param users 用户信息列表
     * @return vo 对象列表
     */
    private static List<UserSearchVo> getSearchVosByUsers(List<User> users) {
        return users.stream().map(user -> {
            // 先拷贝值
            UserSearchVo userSearchVo = new UserSearchVo();
            BeanUtil.copyProperties(user, userSearchVo, "tags");

            // 再解析列表
            userSearchVo.setTags(JSONUtil.parseArray(user.getTags()).toList(String.class));

            // 对描述信息进行压缩
            String description = userSearchVo.getDescription();
            if (StrUtil.isNotBlank(description) && description.length() > UserConstants.MAX_DESCRIPTION_LENGTH) {
                userSearchVo.setDescription(description.substring(0, UserConstants.MAX_DESCRIPTION_LENGTH) + "...");
            }
            return userSearchVo;
        }).toList();
    }

    @Override
    public Result<UserDetailVo> getUserDetailById(Long id) {
        // 查询
        String openId = StpUtil.getLoginIdAsString();
        // 限流，防止刷量
        redissonManager.doRateLimitsOverall(openId, 2L, 1L, RateIntervalUnit.SECONDS, 1L);

        User user = lambdaQuery()
                .select(User::getDescription, User::getWechatId, User::getQqId)
                .eq(User::getId, id)
                .eq(User::getIsDelete, UserConstants.UN_DELETED)
                .one();
        UserDetailVo userDetailVo = BeanUtil.copyProperties(user, UserDetailVo.class);
        return Result.success(userDetailVo);
    }

    @Override
    public Result<PageResult<UserSearchVo>> searchUserByKeyword(UserSearchByKeywordDto userSearchByKeywordDto) {
        PageResult<UserSearchVo> pageResult =
                this.searchUserByKeywordMySQL(userSearchByKeywordDto);
        return Result.success(pageResult);
    }

    /**
     * MYSQL 用户搜索
     * @param userSearchByKeywordDto 分页搜索数据封装
     * @return 用户列表
     */
    private PageResult<UserSearchVo> searchUserByKeywordMySQL(UserSearchByKeywordDto userSearchByKeywordDto) {
        // TODO ES 性能优化
        String keyword = userSearchByKeywordDto.getKeyword();
        String openId = StpUtil.getLoginIdAsString();

        Page<User> pageQuery = Page.of(userSearchByKeywordDto.getPage(), userSearchByKeywordDto.getPageSize());
        // 添加排序条件
        pageQuery.addOrder(OrderItem.desc("create_time"));

        // 分页查询
        pageQuery = lambdaQuery()
                .select(User::getId, User::getUsername, User::getGender, User::getAvatar, User::getDescription, User::getTags, User::getCreateTime, User::getUpdateTime)
                .like(User::getUsername, keyword)
                .or()
                .like(User::getDescription, keyword)
                .or()
                .like(User::getTags, keyword)
                .ne(User::getOpenId, openId)
                .eq(User::getIsDelete, UserConstants.UN_DELETED)
                .page(pageQuery);

        // User 对象转搜索对象
        List<User> users = pageQuery.getRecords();
        List<UserSearchVo> searchVos = getSearchVosByUsers(users);

        // 设置分页结果
        PageResult<UserSearchVo> pageResult = new PageResult<>();
        pageResult.setTotal(pageQuery.getTotal());
        pageResult.setRecords(searchVos);

        return pageResult;
    }

    /**
     * 根据标签列表查找对应的用户
     * @param tags 标签列表
     * @return 用户信息列表
     */
    @Deprecated(since = "该方法和 AI 智搜方法一同被废弃")
    private List<UserSearchVo> searchUserByTags(List<String> tags) {
        String openId = StpUtil.getLoginIdAsString();

        LambdaQueryChainWrapper<User> wrapper = lambdaQuery();

        if (ObjectUtil.isNotEmpty(tags)) {
            for (int i = 0; i < tags.size(); i++) {
                String tag = tags.get(i);
                // 第一个条件不需要or
                if (i == 0) {
                    wrapper.like(User::getTags, tag);
                }
                else {
                    wrapper.or().like(User::getTags, tag);
                }
            }
        }

        wrapper
            .ne(User::getOpenId, openId)
            .eq(User::getIsDelete, UserConstants.UN_DELETED);

        // 查数据
        List<User> users = wrapper.orderByDesc(User::getCreateTime).list();
        return getSearchVosByUsers(users);
    }

    /**
     * AI智能搜索
     * @param userSearchByKeywordDto 用户搜索数据封装
     * @return 用户列表
     */
    @Deprecated(since = "AI 智搜功能合并到助手聊天中")
    private PageResult<UserSearchVo> searchUserByKeywordAI(UserSearchByKeywordDto userSearchByKeywordDto) {
        // 拿出keyword，并去掉 /
        String originalKeyWord = userSearchByKeywordDto.getKeyword();
        String keyword = originalKeyWord.substring(1);

        // 对接 AI
        /*List<String> tags = chatClient.prompt(keyword)
                .advisors(new SimpleLoggerAdvisor())
                // .tools(deepSeekSearchUtil)
                .call()
                .entity(new ParameterizedTypeReference<List<String>>() {});*/

        List<String> tags = null;

        // 根据标签分页查用户
        List<UserSearchVo> userSearchVos = searchUserByTags(tags);

        PageResult<UserSearchVo> pageResult = new PageResult<>();
        pageResult.setTotal((long) userSearchVos.size());
        pageResult.setRecords(userSearchVos);
        return pageResult;
    }
}